package com.enjoyor.health.HGPT.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.Week;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.enjoyor.health.HGPT.core.config.Constant;
import com.enjoyor.health.HGPT.core.config.ParamConfig;
import com.enjoyor.health.HGPT.core.dto.*;
import com.enjoyor.health.HGPT.core.entity.*;
import com.enjoyor.health.HGPT.core.enums.PackageType;
import com.enjoyor.health.HGPT.core.mapper.ReservationConfigurationMapper;
import com.enjoyor.health.HGPT.core.service.*;
import com.enjoyor.health.HGPT.web.CodeEnum;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

import java.nio.file.Files;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Zhu Min
 * @date 2023/10/19 15:50
 */
@Slf4j
@Service
public class BizReservationService {
    @Autowired
    private IUserInfoService userInfoService;
    @Autowired
    private IReservationInfoService reservationInfoService;
    @Autowired
    private IReportRecordService reportRecordService;
    @Autowired
    private ParamConfig paramConfig;
    @Autowired
    private IPhysicalExaminationPackageService physicalExaminationPackageService;
    @Autowired
    private IUserInfoMiniappService userInfoMiniappService;
    @Autowired
    private IReservationConfigurationService reservationConfigurationService;

    /**
     * 发预约号的锁
     */
    private static final ReentrantLock NUMBER_LOCK = new ReentrantLock(true);

    public UserInfo getUserInfoByOpenId(String openId){
        return userInfoService.getUserInfoFromOpenId(openId);
    }

    public JSONArray getScheduleInfo(String openId){
        UserInfo userInfo = userInfoService.getUserInfoFromOpenId(openId);

        List<ScheduleInfoDto> scheduleInfoDtoList = reservationInfoService.getReservationInfoByOpenId(openId);
        JSONArray resultArray = new JSONArray();
        for (ScheduleInfoDto scheduleInfoDto : scheduleInfoDtoList) {
            String url = null;
            if (StringUtils.isNotBlank(scheduleInfoDto.getReportVersion())){
                List<ReportRecord> reportRecordList = reportRecordService.getReportRecordByOpenId(openId, scheduleInfoDto.getReportVersion(), scheduleInfoDto.getPackageType());
                if (CollUtil.isNotEmpty(reportRecordList)){
                    for (ReportRecord reportRecord : reportRecordList) {
                        String path = paramConfig.getPdfPath() + reportRecord.getId() + ".pdf";
                        if (FileUtil.exist(path)){
                            url = path;
                            log.info("用户{}的{}版本报告已生成pdf版", openId, scheduleInfoDto.getReportVersion());
                        }else {
                            log.info("用户{}的{}版本报告未生成pdf版！", openId, scheduleInfoDto.getReportVersion());
                        }
                    }
                }else {
                    log.info("用户{}的{}版本报告未生成！", openId, scheduleInfoDto.getReportVersion());
                }
            }
            JSONObject result = new JSONObject();
            result.put("name", scheduleInfoDto.getName());
            result.put("location", scheduleInfoDto.getReservationLocation());
            result.put("time", scheduleInfoDto.getTime());
            result.put("number", scheduleInfoDto.getReservationNumber());
            result.put("state", scheduleInfoDto.getFlag());
            result.put("version", scheduleInfoDto.getReportVersion());
            result.put("packageType", scheduleInfoDto.getPackageType());
            result.put("reportUrl", url);
            resultArray.add(result);
        }
        return resultArray;
    }

    public JSONObject getDbAppointment(String openId, String date, String period, String packageType){
        NUMBER_LOCK.lock();
        try {
            LocalDateTime now = LocalDateTime.now();
            JSONObject ret = new JSONObject();
            //查询用户的性别，根据体检包的性别匹配体检包ID，如果没有性别的，就用同packageType
            List<PhysicalExaminationPackage> physicalExaminationPackage =
                    physicalExaminationPackageService.getByPackageType(Integer.parseInt(packageType));
            UserInfo userInfo = userInfoService.getUserInfoFromOpenId(openId);
            int gender = 0;
            if (userInfo != null){
                gender = userInfo.getGender();
            }
            String packageId = null;
            if (CollUtil.isNotEmpty(physicalExaminationPackage)){
                int finalGender = gender;
                Optional<PhysicalExaminationPackage> optionalPhysicalExaminationPackage = physicalExaminationPackage.stream().filter(x->x.getGender() == finalGender).findFirst();
                if (optionalPhysicalExaminationPackage.isPresent()){
                    packageId = optionalPhysicalExaminationPackage.get().getId();
                }else {
                    packageId = physicalExaminationPackage.get(0).getId();
                }
            }
            List<UserInfoMiniapp> userInfoMiniapps = userInfoMiniappService.getReservationUserList(date, period, Integer.parseInt(packageType));
            int num = userInfoMiniapps.size();
            UserInfoMiniapp userInfoMiniapp = userInfoMiniappService.getUserInfoMiniappByOpenId(openId);
            String userId = userInfoMiniapp.getUserId();
            String reservationNumber =
                    String.format("%02d", Integer.parseInt(packageType))
                    + date.replaceAll("-", "")
                    + period.substring(0,2)
                    + "000"
                    + (num+1);

            if (CollUtil.isNotEmpty(userInfoMiniapps)){
                Set<String> openIdSet = userInfoMiniapps.stream().map(UserInfoMiniapp::getOpenId)
                        .collect(Collectors.toSet());
                if (openIdSet.contains(openId)){
                    ret.put("code", CodeEnum.RESERVATION_EXIST);
                    UserInfoMiniapp user = userInfoMiniapps.stream().filter(x->x.getOpenId().equals(openId)).findFirst().get();
                    //后面弄成dto，暂时放nickname里预约号
                    ret.put("number", user.getNickName());
                    return ret;
                }else {
                    //最大预约数量
                    LocalDate rLd = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                    Optional<ReservationConfigNumDto> rOpt = reservationConfigurationService.getPeriodSumByPackageType(rLd.getDayOfWeek().getValue(), packageType)
                            .stream().filter(x->period.equals(x.getReservationPeriod()))
                            .findFirst();

                    if (userInfoMiniapps.size()>= (rOpt.isPresent()?rOpt.get().getSum() : 6)){
                        ret.put("code", CodeEnum.RESERVATION_MAX);
                        return ret;
                    }else {
                        //插入
                        ReservationInfo reservationInfo = new ReservationInfo();
                        reservationInfo.setId(IdUtil.simpleUUID());
                        reservationInfo.setUserId(userId);
                        reservationInfo.setReservationNumber(reservationNumber);
                        reservationInfo.setReservationDate(date);
                        reservationInfo.setReservationPeriod(period);
                        reservationInfo.setStorageTime(LocalDateTime.now());
                        reservationInfo.setUpdateTime(LocalDateTime.now());
                        reservationInfo.setReservationLocation(Constant.RESERVATION_LOCATION);
                        reservationInfo.setReservationPackageId(packageId);
                        reservationInfoService.save(reservationInfo);
                        ret.put("number", reservationNumber);
                    }
                }
            }else {
                ReservationInfo reservationInfo = new ReservationInfo();
                reservationInfo.setId(IdUtil.simpleUUID());
                reservationInfo.setUserId(userId);
                reservationInfo.setReservationNumber(reservationNumber);
                reservationInfo.setReservationDate(date);
                reservationInfo.setReservationPeriod(period);
                reservationInfo.setStorageTime(LocalDateTime.now());
                reservationInfo.setUpdateTime(LocalDateTime.now());
                reservationInfo.setReservationLocation(Constant.RESERVATION_LOCATION);
                reservationInfo.setReservationPackageId(packageId);
                reservationInfoService.save(reservationInfo);
                ret.put("number", reservationNumber);
            }
            ret.put("code", CodeEnum.SUCCESS);
            return ret;
        }finally {
            NUMBER_LOCK.unlock();
        }
    }

    public JSONArray reservationAvailableSevenDay(String openId){
        JSONArray result = new JSONArray();
        LocalDateTime now = LocalDateTime.now();
        String curDate = LocalDateTimeUtil.format(now, Constant.DATE_TIME_FORMAT);
        //这里多查一天也没关系。。
        List<ReservationNumDto> reservationNumDtos = reservationInfoService
                .getReservationNumBetweenDate(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),now.plusDays(7).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        Map<String, ReservationNumDto> reservationNumDtoMap = reservationNumDtos.stream()
                .collect(Collectors.toMap(ReservationNumDto::getDate, Function.identity()));
        for (LocalDateTime n = now; n.isBefore(now.plusDays(7)); n = n.plusDays(1)){
            JSONObject available = new JSONObject();
            available.put("checked", false);
            available.put("date", n.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            available.put("day", n.getDayOfMonth());
            available.put("weekday", n.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.CHINESE).replace("星期", "周"));
            String date = n.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            int num = reservationNumDtoMap.get(date) == null? 0 : reservationNumDtoMap.get(date).getResNum();
            if (num >=42){
                available.put("available", "无号");
            }else {
                available.put("available", "有号");
            }
            result.add(available);
        }
        return result;
    }

    public JSONArray reservationAvailableSingleDay(String date){
        List<String> periodKey = Lists.newArrayList("10:00-11:00", "11:00-12:00", "13:00-14:00", "14:00-15:00", "15:00-16:00", "16:00-17:00");
        List<ReservationNumDto> reservationNumDtoList = reservationInfoService.getReservationNumByDate(date);
        Map<String, ReservationNumDto> reservationNumDtoMap = reservationNumDtoList.stream()
                .collect(Collectors.toMap(ReservationNumDto::getTime, Function.identity()));

        JSONArray available = new JSONArray();
        for (int i = 0; i < periodKey.size(); i++) {
            JSONObject data = new JSONObject();
            data.put("checked", false);
            data.put("time", periodKey.get(i));
            data.put("key", i+1);
            ReservationNumDto dto = reservationNumDtoMap.get(periodKey.get(i));
            if (dto == null){
                data.put("count", 6);
            }else {
                data.put("count", 6 - dto.getResNum());
            }
            available.add(data);
        }
        return available;
    }

    public Page<Map<String, Object>> reservationWeb(String pkgType,
                                                    String startDate,
                                                    String endData,
                                                    String period,
                                                    String status, int page, int size){
        return reservationInfoService.getReservationInfoWeb(LocalDate.now(),pkgType,
                startDate,
                endData,
                period,
                status, page, size);
    }

    public void updateReservationInfoPresentTime(String reservationNumber){
        reservationInfoService.updateReservationInfoUpdateTime(reservationNumber);
    }

    private List<String> getPeriodList(){
        List<String> periodList = Lists.newArrayList("10:00-11:00",
                "11:00-12:00",
                "13:00-14:00",
                "14:00-15:00",
                "15:00-16:00",
                "16:00-17:00");
        return periodList;
    }

    public ReservationWeekDto getReservationConfigByPackageType(String packageType){
        List<ReservationConfiguration> reservationConfigurationList = reservationConfigurationService.getByType(packageType);
        Map<String, List<ReservationConfiguration>> configMap = reservationConfigurationList
                .stream().collect(Collectors.groupingBy(x->x.getWeekday() + "_" + x.getReservationPeriod()));

        ReservationWeekDto dto = new ReservationWeekDto();
        if (CollUtil.isNotEmpty(reservationConfigurationList)){
            dto.setPackageType(packageType);
            dto.setPackageTypeName(StringUtils.isBlank(packageType)? "ALL" : reservationConfigurationList.get(0).getPackageTypeName());
            for (int i=1; i<=7; i++){
                ReservationWeekDto.ReservationDayDto dayDto = new ReservationWeekDto.ReservationDayDto();
                dayDto.setIndex(i);
                dayDto.setDay(Week.of(i==7 ? 1:i+1).toChinese());
                for (int p=0; p< getPeriodList().size(); p++){
                    String key = i + "_" + getPeriodList().get(p);
                    List<ReservationConfiguration> configuration = configMap.get(key);
                    ReservationWeekDto.ReservationDayDto.ReservationPeriodDto periodDto = new ReservationWeekDto.ReservationDayDto.ReservationPeriodDto();
                    periodDto.setIndex(p + 1);
                    periodDto.setPeriod(getPeriodList().get(p));
                    periodDto.setNumber(0);
                    if (configuration != null){
                        periodDto.setNumber(configuration.stream().mapToInt(ReservationConfiguration::getReservationNumber).sum());
                    }
                    dayDto.getPeriodMap().put(periodDto.getIndex(), periodDto);
                }
                dto.getDayMap().put(dayDto.getIndex(), dayDto);
            }
        }
        return dto;
    }

    public ReservationWeekDtoV2.ReservationDayDto getReservationConfigByWeekDay(String weekDay){
        List<ReservationConfiguration> reservationConfigurationList = reservationConfigurationService.getByWeekDay(weekDay);
        Map<String, ReservationConfiguration> configMap = reservationConfigurationList
                .stream().collect(Collectors.toMap(x->x.getPackageType() + "_" + x.getReservationPeriod(), Function.identity(), (v1,v2)->v2));

        ReservationWeekDtoV2.ReservationDayDto dayDto = new ReservationWeekDtoV2.ReservationDayDto();
        int i = Integer.parseInt(weekDay);
        dayDto.setIndex(i);
        dayDto.setDay(Week.of(i==7 ? 1:i+1).toChinese());
        for (int p=0; p< getPeriodList().size(); p++){
            ReservationWeekDtoV2.ReservationDayDto.ReservationPeriodDto periodDto = new ReservationWeekDtoV2.ReservationDayDto.ReservationPeriodDto();
            periodDto.setIndex(p + 1);
            periodDto.setPeriod(getPeriodList().get(p));

            for (int type=1; type<=3; type ++){
                ReservationWeekDtoV2.ReservationDayDto.ReservationPeriodDto.TypeNumberDto typeNumberDto = new ReservationWeekDtoV2.ReservationDayDto.ReservationPeriodDto.TypeNumberDto();
                typeNumberDto.setIndex(type);
                typeNumberDto.setPackageType(type);
                typeNumberDto.setPackageTypeNme(PackageType.getTypeName(type));
                ReservationConfiguration configuration = configMap.get(type + "_" + periodDto.getPeriod());
                if (configuration == null){
                    typeNumberDto.setNumber(0);
                }else {
                    typeNumberDto.setNumber(configuration.getReservationNumber());
                }
                periodDto.getTypeNumberMap().put(type, typeNumberDto);
            }
            dayDto.getPeriodMap().put(periodDto.getIndex(), periodDto);
        }
        return dayDto;
    }

    public void updateReservationConfig(ReservationWeekDtoV2.ReservationDayDto reservationDayDto){
        reservationConfigurationService.deleteByWeekDay(reservationDayDto.getIndex(), null);

        reservationDayDto.getPeriodMap().forEach((k,v)->{
            v.getTypeNumberMap().forEach((pK,pV)->{
                ReservationConfiguration configuration = new ReservationConfiguration();
                configuration.setWeekday(reservationDayDto.getIndex());
                configuration.setPackageType(pV.getPackageType());
                List<PhysicalExaminationPackage> packageList = physicalExaminationPackageService.getByPackageType(pV.getPackageType());

                configuration.setPackageTypeName(CollUtil.isNotEmpty(packageList)? packageList.get(0).getPackageTypeName() : "");
                configuration.setReservationPeriod(getPeriodList().get(v.getIndex()-1));
                configuration.setReservationNumber(0);
                configuration.setStorageTime(LocalDateTime.now());
                configuration.setUpdateTime(LocalDateTime.now());
                configuration.setReservationNumber(pV.getNumber());
                reservationConfigurationService.save(configuration);
            });
        });
    }


    public void updateReservationConfig(ReservationWeekDto.ReservationDayDto reservationDayDto, Integer packageType){
        //删除当天的，重新插入当天的
        reservationConfigurationService.deleteByWeekDay(reservationDayDto.getIndex(), packageType);
        for (int i=1; i<=6; i++){
            ReservationWeekDto.ReservationDayDto.ReservationPeriodDto dto = reservationDayDto.getPeriodMap().get(i);
            ReservationConfiguration configuration = new ReservationConfiguration();
            configuration.setWeekday(reservationDayDto.getIndex());
            configuration.setPackageType(packageType);

            List<PhysicalExaminationPackage> packageList = physicalExaminationPackageService.getByPackageType(packageType);

            configuration.setPackageTypeName(CollUtil.isNotEmpty(packageList)? packageList.get(0).getPackageTypeName() : "");
            configuration.setReservationPeriod(getPeriodList().get(i-1));
            configuration.setReservationNumber(0);
            configuration.setStorageTime(LocalDateTime.now());
            configuration.setUpdateTime(LocalDateTime.now());
            if (dto != null){
                //configuration.setReservationPeriod(dto.getPeriod());
                configuration.setReservationNumber(dto.getNumber());
            }
            reservationConfigurationService.save(configuration);
        }
    }

    public JSONArray reservationAvailableSevenDayV2(String openId, String packageType){
        JSONArray result = new JSONArray();
        LocalDateTime now = LocalDateTime.now();
        String curDate = LocalDateTimeUtil.format(now, Constant.DATE_TIME_FORMAT);
        //这里多查一天也没关系。。
        List<PhysicalExaminationPackage> packageList = physicalExaminationPackageService.getByPackageType(Integer.parseInt(packageType));

        List<ReservationNumDto> reservationNumDtos = reservationInfoService
                .getReservationNumBetweenDateV2(
                        now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
                        now.plusDays(7).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
                        packageList.stream().map(PhysicalExaminationPackage::getId).collect(Collectors.toList())
                        );
        //按日期MAP
        Map<String, ReservationNumDto> reservationNumDtoMap = reservationNumDtos.stream()
                .collect(Collectors.toMap(ReservationNumDto::getDate, Function.identity()));
        Map<Integer, ReservationConfigNumDto> reservationConfigNumDtoMap = reservationConfigurationService.getSumByPackageType(packageType)
                .stream().collect(Collectors.toMap(ReservationConfigNumDto::getWeekday, Function.identity(), (k1,k2)->k2));

        for (LocalDateTime n = now; n.isBefore(now.plusDays(7)); n = n.plusDays(1)){
            JSONObject available = new JSONObject();
            available.put("checked", false);
            available.put("date", n.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            available.put("day", n.getDayOfMonth());
            available.put("weekday", n.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.CHINESE).replace("星期", "周"));
            String date = n.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            //获取当天已经总号数，是否有剩余
            int num = reservationNumDtoMap.get(date) == null? 0 : reservationNumDtoMap.get(date).getResNum();
            //当天可有的号数
            int dayTotal = reservationConfigNumDtoMap.get(n.getDayOfWeek().getValue()) == null?
                    0 : reservationConfigNumDtoMap.get(n.getDayOfWeek().getValue()).getSum();
            if (num >=dayTotal){
                available.put("available", "无号");
            }else {
                available.put("available", "有号");
            }
            //当天剩下时间无号判断
            if (n.getDayOfMonth() == LocalDate.now().getDayOfMonth()){
                JSONArray todayResrv = reservationAvailableSingleDayV2(n.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), packageType);
                //剩余总数
                int leftTotal = 0;
                for (Object o : todayResrv) {
                    JSONObject time = (JSONObject) o;
                    int c = time.getIntValue("count", 0);
                    leftTotal = leftTotal + c;
                }
                if (leftTotal == 0){
                    available.put("available", "无号");
                }
            }
            result.add(available);
        }
        return result;
    }

    public JSONArray reservationAvailableSingleDayV2(String date, String packageType){
        List<String> periodKey = Lists.newArrayList("10:00-11:00", "11:00-12:00", "13:00-14:00", "14:00-15:00", "15:00-16:00", "16:00-17:00");
        List<ReservationNumDto> reservationNumDtoList = reservationInfoService.getReservationNumByDateV2(date, Lists.newArrayList(packageType));
        Map<String, ReservationNumDto> reservationNumDtoMap = reservationNumDtoList.stream()
                .collect(Collectors.toMap(ReservationNumDto::getTime, Function.identity()));

        LocalDate ld = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        Integer weekDay = ld.getDayOfWeek().getValue();

        Map<String, ReservationConfigNumDto> sumTotalMap = reservationConfigurationService.getPeriodSumByPackageType(weekDay, packageType)
                .stream().collect(Collectors.toMap(ReservationConfigNumDto::getReservationPeriod, Function.identity(), (k1,k2)->k2));
        JSONArray available = new JSONArray();
        for (int i = 0; i < periodKey.size(); i++) {
            JSONObject data = new JSONObject();
            //
            boolean checked = false;
            data.put("checked", checked);


            data.put("time", periodKey.get(i));
            data.put("key", i+1);
            ReservationNumDto dto = reservationNumDtoMap.get(periodKey.get(i));
            if (dto == null){
                data.put("count", sumTotalMap.get(periodKey.get(i)) == null ? 0 : sumTotalMap.get(periodKey.get(i)).getSum());
            }else {
                data.put("count", Math.max((sumTotalMap.get(periodKey.get(i)) == null ? 0 : sumTotalMap.get(periodKey.get(i)).getSum()) - dto.getResNum(), 0));
            }
            LocalDateTime nowLdt = LocalDateTime.now();
            if (DateTimeFormatter.ofPattern("yyyy-MM-dd").format(nowLdt).equals(date)){
                //过去的时间置灰
                if (i == 0 ){
                    if ((nowLdt.getHour()) >=11)
                        data.put("count", 0);
                    if (nowLdt.getHour() ==10)
                        data.put("checked", true);
                }
                if (i == 1){
                    if ((nowLdt.getHour()) >=12)
                        data.put("count", 0);
                    if (nowLdt.getHour() ==11)
                        data.put("checked", true);
                }
                if (i == 2){
                    if ((nowLdt.getHour()) >=14)
                        data.put("count", 0);
                    if (nowLdt.getHour() ==13)
                        data.put("checked", true);
                }
                if (i == 3){
                    if ((nowLdt.getHour()) >=15)
                        data.put("count", 0);
                    if (nowLdt.getHour() ==14)
                        data.put("checked", true);
                }
                if (i == 4){
                    if ((nowLdt.getHour()) >=16)
                        data.put("count", 0);
                    if (nowLdt.getHour() ==15)
                        data.put("checked", true);
                }
                if (i == 5){
                    if ((nowLdt.getHour()) >=17)
                        data.put("count", 0);
                    if (nowLdt.getHour() ==16)
                        data.put("checked", true);
                }
                //
            }
            data.put("checked", false);
            available.add(data);
        }
        return available;
    }

}
